﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Windows.Markup;

using agree.schema;

using alib;
using alib.Collections;
using alib.Debugging;
using alib.Enumerable;
using alib.Reflection;

namespace agree
{
	public interface sync_obj_ctor_arg { }

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public abstract class schema_obj_base : ISchemaObj
	{
		public Object GetValue(schema_prop sp)
		{
			return sp.GetValue(this, 0, null, null, null);
		}
		public void SetValue(schema_prop sp, Object value)
		{
			sp.SetValue(this, value, 0, null, null, null);
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[RuntimeNameProperty("Name")]
	//[DebuggerTypeProxy(typeof(_iidentity_debug))]
	public abstract class identity_base : schema_obj_base, IIdentity
	{
		protected identity_base(sync_obj_ctor_arg _) { }
		protected identity_base(IIdentity trace)
		{
#if DEBUG
			if (trace == null)
			{
				if (Debugger.IsAttached)
					Debug.Print("Null trace: {0}", this.GetType().Name);
				Debugger.Break();
			}
#endif
			this.trace = trace;
		}

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		public IIdentity trace;
		//[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		[DebuggerDisplay("{_identity_extensions._dbg_trace(this),nq}")]
		public IIdentity Trace { [DebuggerStepThrough] get { return trace; } }
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public abstract class identity_seq_child : identity_seq, IRtChild
	{
		protected identity_seq_child(sync_obj_ctor_arg _) : base(_) { }
		protected identity_seq_child(IRtParent parent, IIdentity prv)
			: base(prv ?? parent)
		{
			this.parent = parent;
		}

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		protected readonly IRtParent parent;
		public IRtParent Parent { [DebuggerStepThrough] get { return parent; } }
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public abstract class identity_node : identity_base, IRtNodeVolatile
	{
		public identity_node(IRtParent parent, IIdentity prv)
			: base(prv)
		{
			this.parent = parent;
		}
		public identity_node(IRtParent parent)
			: this(parent, parent)
		{
		}

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		protected readonly IRtParent parent;
		public IRtParent Parent { [DebuggerStepThrough] get { return parent; } }

		public TaskController TaskControl { get { return parent.TaskControl; } }

		public INameScope NameScope { get { return parent.NameScope; } }

		public object GetService(Type serviceType) { return parent.GetService(serviceType); }
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[DebuggerDisplay("{ToString(),nq}")]
	public abstract class identity_node_named : identity_node, ρρName
	{
		public identity_node_named(IRtParent parent, IIdentity prv)
			: base(parent, prv)
		{
		}
		public identity_node_named(IRtParent parent, IIdentity prv, String name)
			: this(parent, prv)
		{
			this.name = name;
		}
		public identity_node_named(IRtParent parent)
			: this(parent, parent)
		{
		}

		///////////////////////////////////////////////////////////////////////
		/// 
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		String name;
		public String Name
		{
			[DebuggerStepThrough]
			get { return this.name; }
			[DebuggerStepThrough]
			set { if (value != this.name) { this.name = value; } }
		}
		/// 
		///////////////////////////////////////////////////////////////////////

		public override String ToString()
		{
			String s = name;
			if (s != null)
				s = "[" + s + "] ";
			return s + base.ToString();
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public abstract class identity_named : identity_base, ρρName
	{
		public identity_named(IIdentity prv)
			: base(prv)
		{
		}
		public identity_named(IIdentity prv, String name)
			: this(prv)
		{
			this.name = name;
		}

		///////////////////////////////////////////////////////////////////////
		/// 
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		String name;
		public String Name
		{
			[DebuggerStepThrough]
			get { return this.name; }
			[DebuggerStepThrough]
			set { this.name = value; }
		}
		/// 
		///////////////////////////////////////////////////////////////////////
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public abstract class identity_propchange : identity_named, INotifyPropertyChanged
	{
		protected identity_propchange(IIdentity prv)
			: base(prv)
		{
		}

		public identity_propchange(IIdentity prv, String name)
			: base(prv, name)
		{
		}

		protected void NotifyPropertyChanged(String s_field)
		{
			PropertyChangedEventHandler h = PropertyChanged;
			if (h != null)
				h(this, new PropertyChangedEventArgs(s_field));
		}

		public event PropertyChangedEventHandler PropertyChanged;
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public abstract class identity_named_init : identity_node_named
	{
		public identity_named_init(IRtParent parent, IIdentity prv)
			: base(parent, prv)
		{
		}
		public identity_named_init(IRtParent parent, IIdentity prv, String name)
			: base(parent, prv, name)
		{
		}

		/// <summary>
		/// to avoid clutter, we stub out unwanted functions in case our inheritor chooses to implement ISupportInitializeNotification
		/// </summary>
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		public bool IsInitialized { get { return false; } }
		public event EventHandler Initialized;
		public void BeginInit() { }
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	//[DebuggerDisplay("{_iidentity_debug.value_display(this.value),nq}",
	//Name = "{spa._dbg_membername(item),nq}",
	//Type = "{spa.Type.Name,nq}")]
	public struct item_property_value
	{
		public item_property_value(ISchemaObj item, schema_prop_attachable spa)
		{
			this.spa = spa;
			if ((this.value = spa.GetValue(item)) != null)
				this.item = item;
			else
				this.item = default(IIdentity);
		}
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		public ISchemaObj item;
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		public schema_prop_attachable spa;
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		public Object value;

		public bool try_get(ISchemaObj item, schema_prop_attachable spa)
		{
			return (this.value = (this.spa = spa).GetValue(this.item = item)) != null;
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public sealed class identity_busy : identity_base
	{
		public identity_busy(IIdentity prv) : base(prv) { }
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public sealed class identity_pair<T> : identity_base, IIdentPair<T>
		where T : class		// if removing this constraint, then do not use 'readonly' on the fields
	{
		public identity_pair(IIdentity prv, T x, T y)
			: base(prv)
		{
			this.x = x;
			this.y = y;
		}
		public readonly T x;
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		T IIdentPair<T>.x { [DebuggerStepThrough] get { return this.x; } }

		public readonly T y;
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		T IIdentPair<T>.y { [DebuggerStepThrough] get { return this.y; } }

		public override String ToString()
		{
			var xs = x == null ? "(null)" : alib.String._string_ext.CondenseSpaces(x.ToString());
			var ys = y == null ? "(null)" : alib.String._string_ext.CondenseSpaces(y.ToString());
			return String.Format("x={0}, y={1}", xs, ys);
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[DebuggerDisplay("{ToString(),nq}")]
	public class identity_group<TKey, TElement> : identity_base, IIdentGroup<TKey, TElement>, ρρItemsArr
		where TElement : class, IIdentity
	{
		public identity_group(IIdentity prv, TKey key, TElement[] arr)
			: base(prv)
		{
			this.key = key;
			this.arr = arr;
		}
		public identity_group(IIdentity prv, TKey key, IItems<TElement> seq)
			: this(prv, key, seq.ToArray())
		{
		}
		[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
		readonly TElement[] arr;
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		IIdentity[] ρρItemsArr.ItemsArr { get { return arr; } }

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		TKey key;
		public TKey Key { get { return this.key; } }
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		Object IGrouping.Key { get { return this.key; } }

		public IEnumerator<TElement> GetEnumerator() { return arr.Enumerator(); }

		IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }

		public int Count { get { return arr.Length; } }

		public TElement this[int index] { get { return arr[index]; } }

		public void CopyTo(Array array, int index) { arr.CopyTo(arr, index); }

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		bool ICollection.IsSynchronized { get { return false; } }
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		Object ICollection.SyncRoot { get { return this; } }

		public override String ToString()
		{
			return String.Format("< {0}, {{ {1} (count:{2}) }} >", typeof(TKey)._Name(), typeof(TElement)._Name(), Count);
		}
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[DebuggerDisplay("{ToString(),nq}")]
	public class identity_master_group<T> : identity_group<T, T>, IIdentMasterGroup<T>
		where T : class, IIdentity
	{
		public identity_master_group(IIdentity prv, T key, IItems<T> seq)
			: base(prv, key, seq)
		{
		}
		public identity_master_group(IIdentity prv, T key, T[] seq)
			: base(prv, key, seq)
		{
		}

#if DEBUG
		[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
		public T[] _dbg { get { return (T[])((ρρItemsArr)this).ItemsArr; } }
#endif
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[DebuggerDisplay("rows:{c_rows} columns:{c_cols}")]
	public class identity_matrix : alib.Matrix.Matrix, IIdentity
	{
		public identity_matrix(IIdentity prv, double[,] v)
			: base(v)
		{
			this.prv = prv;
		}
		public identity_matrix(IIdentity prv, int c_rows, int c_cols)
			: this(prv, new double[c_rows, c_cols])
		{
		}

		readonly IIdentity prv;
		public IIdentity Trace { get { return prv; } }
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// agree collections and materialization
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public static class _identity_extensions
	{
		/// <summary> won't get Collection(T) </summary>
		public static int CountIfAvail(this Object _this)
		{
			return
				_this == null ? -1 :
					_this is Array ? ((Array)_this).Length :
						_this is IReadOnlyCollection<Object> ? ((IReadOnlyCollection<Object>)_this).Count :
							_this is ICollection ? ((ICollection)_this).Count :
								-1;
		}

		public static int CountIfAvail<T>(this IItems<T> _this)
		{
			return _this is IReadOnlyCollection<T> ? ((IReadOnlyCollection<T>)_this).Count :
				_this is ICollection<T> ? ((ICollection<T>)_this).Count :
					_this is ICollection ? ((ICollection)_this).Count :
						_this is ρρOutputItemsCount && ((IMonad)_this).IsCompleted ? ((ρρOutputItemsCount)_this).OutputItemsCount :
							-1;
		}

		public static T[] ToArray<T>(this IItems<T> _this)
		{
			IdentArray<T> t0;
			if ((t0 = _this as IdentArray<T>) != null)
				return t0.arr;

			RefList<T> t1;
			if ((t1 = _this as RefList<T>) != null)
				return t1.GetTrimmed();

			ρρItemsArr t4;
			T[] rgi;
			if ((t4 = _this as ρρItemsArr) != null && (rgi = t4.ItemsArr as T[]) != null)
				return rgi;

			IIdentList<T> t2;
			if ((t2 = _this as IIdentList<T>) != null)
			{
				T[] arr = new T[t2.Count];
				t2.CopyTo(arr, 0);
				return arr;
			}

			Debug.Assert(_this.CountIfAvail() == -1);

			IActiveObj<T> t3;
			if ((t3 = _this as IActiveObj<T>) != null)
				return alib.Observable._obs_ext.ToArrayWait(t3);

			throw new Exception();
		}

		public static IIdentList<T> ToIdentList<T>(this IItems<T> _this)
		{
			var idl = _this as IIdentList<T>;
			if (idl != null)
				return idl;

			T[] arr = null;
			var pia = _this as ρρItemsArr;
			if (pia != null)
				arr = pia.ItemsArr as T[];

			if (arr == null)
				arr = ToArray(_this);

			return new IdentArray<T>(_this, arr);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// agree attached properties
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		public static Object GetValue(this ISchemaObj _this, schema_prop sp)
		{
			return sp.GetValue(_this, 0, null, null, null);
		}

		public static void SetValue(this ISchemaObj _this, schema_prop sp, Object value)
		{
			sp.SetValue(_this, value, 0, null, null, null);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// trace
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		public static schema.Schema FindSchema(this ISchemaObj _this)
		{
			Object sch;
			do
				if (PP.Schema.TryGetItemValue(_this, out sch))
					return (Schema)sch;
			while (_this is IIdentity && (_this = ((IIdentity)_this).Trace) != null);
			return default(Schema);
		}

		public static IRtParent FindParent(this IIdentity _this)
		{
			return FindTrace<IRtParent>(_this);
		}

		public static void ForwardTrace(this IIdentity _this, IIdentity fwd)
		{
			fwd._set_trace(_this.Trace);
			_this._set_trace(fwd);
		}

		[DebuggerStepThrough]
		public static IIdentity[] _get_trace(this IIdentity item, int c)
		{
			c++;
			IIdentity[] ret;
			var prv = item.Trace;
			if (prv != null)
				ret = _get_trace(prv, c);
			else
				ret = new IIdentity[c];
			ret[ret.Length - c] = item;
			return ret;
		}

		public static IEnumerable<IIdentity> WalkTrace(this IIdentity item)
		{
			if (item != null)
				while ((item = item.Trace) != null)
					yield return item;
		}

		public static T FindTrace<T>(this IIdentity item)
			where T : class, IIdentity
		{
			T t;
			while (item != null)
			{
				if ((t = item as T) != null)
					return t;
				item = item.Trace;
			}
			return null;
		}

		public static void _set_trace(this IIdentity _this, IIdentity to_set)
		{
			if (_this is _monad)
				((_monad)_this).trace = to_set;
			else if (_this is identity_base)
				((identity_base)_this).trace = to_set;
			else
				throw new Exception();
		}

		[DebuggerStepThrough]
		public static String _dbg_trace(this IIdentity item)
		{
			return _get_trace(item, 0)
						.Select(x => x == item ? "this" : x.GetType()._Name())
						.StringJoin(" ￫ ");
		}
	};
}